package com.niodata.dp.app;

import com.niodata.dp.app.dao.VariableMapper;
import com.niodata.dp.core.dblock.DbLock;
import com.niodata.dp.task.dao.JobMapper;
import com.niodata.dp.task.dao.PluginPackageMapper2;
import com.niodata.dp.task.entity.JobInfo;
import com.niodata.dp.task.entity.PackageOutParam;
import com.niodata.dp.task.entity.PackageOutParams;
import com.niodata.dp.task.entity.ParamType;
import com.niodata.dp.task.entity.PluginPackage;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * variable manager impl,responsible for variable create,update,bind,unbind,value apply.
 */
@Service("variableManagerImpl")
public class DefaultVariableManager implements VariableManager {

  @Autowired
  PluginPackageMapper2 pluginMapper;
  @Autowired
  JobMapper jobMapper;
  @Autowired
  VariableMapper variableMapper;

  @Autowired
  @Qualifier("mysqlLock")
  DbLock dbLock;

  @Override
  @Transactional
  public void createVariable(String appId, String appCode,
      VariableType type, ParamType paramType,
      String name, String description,
      String taskName, String job, String param, Object initValue) {
    AppVariable variable = new AppVariable();
    variable.setType(type);
    variable.setName(name);
    variable.setAppCode(appCode);
    variable.setDescription(description);
    variable.setAppId(appId);
    variable.setBindJob(job);
    variable.setBindParam(param);
    variable.setBindTask(taskName);
    variable.setUpTime(new Date());
    variable.setParamType(paramType);
    if (variable.getBindParam() != null && variable.getBindParam().length() > 0) {
      variable.setBind(true);
      variable.setBindTime(new Date());
    } else {
      variable.setBindTime(null);
      variable.setBind(false);
    }
    if (variable.isBind()) {
      JobInfo jobInfo = jobMapper.getJobByName(job, taskName);
      PluginPackage plugin =
          pluginMapper.getPluginByNameVersion(jobInfo.getPkgName(), jobInfo.getPkgVersion());
      PackageOutParams outParams = plugin.getOutParams();
      if (outParams != null && outParams.getParamSchemas().size() > 0) {
        for (PackageOutParam outParam : outParams.getParamSchemas()) {
          if (outParam.getName().equals(param)) {
            variable.setParamType(outParam.getType());
            break;
          }
        }
      }
    }
    if (initValue != null) {
      VariableValue value = null;
      if (VariableType.Indicator.equals(type)) {
        IndicatorValue indicatorValue = new IndicatorValue();
        indicatorValue.setValue(initValue);
        indicatorValue.setParamType(paramType);
        value = indicatorValue;
      } else if (VariableType.Accumulator.equals(type)) {
        AccumulatorValue accumulatorValue = new AccumulatorValue();
        accumulatorValue.setValue(initValue);
        accumulatorValue.setParamType(paramType);
        value = accumulatorValue;
      } else {
        QueueValue queueValue = new QueueValue();
        queueValue.setValue(initValue);
        queueValue.setParamType(paramType);
        value = queueValue;
      }
      variable.setCurrentValue(value);

    }
    variableMapper.save(variable);

  }

  @Override
  public void update(AppVariable appVariable) {
    String lock = lockName(appVariable.getAppId(), appVariable.getName());
    try {
      this.dbLock.tryLock(lock);
      this.variableMapper.update(appVariable);
    } finally {
      this.dbLock.releaseLock(lock);
    }
  }

  @Override
  @Transactional
  public void deleteVariable(String appId, String name) {
    this.variableMapper.delete(appId, name);
  }


  @Override
  @Transactional
  public void bindVariable(String appId, String varName, String taskId, String jobName,
      String jobParam) {
    try {
      this.dbLock.tryLock(this.lockName(appId, varName));
      AppVariable variable = variableMapper.getByName(appId, varName);
      variable.setBindTask(taskId);
      variable.setBindJob(jobName);
      variable.setBindParam(jobParam);
      variable.setBindTime(new Date());
      variable.setBind(true);
      variableMapper.update(variable);

    } finally {
      this.dbLock.releaseLock(this.lockName(appId, varName));
    }

  }

  @Override
  @Transactional
  public void unBindVariable(String appId, String varName) {
    String lockName = this.lockName(appId, varName);
    try {
      dbLock.tryLock(lockName);
      AppVariable variable = variableMapper.getByName(appId, varName);
      variable.setBind(false);
      variable.setBindParam(null);
      variable.setBindJob(null);
      variable.setBindTask(null);
      variable.setBindTime(new Date(0));
      variableMapper.update(variable);
    } finally {
      dbLock.releaseLock(lockName);
    }


  }

  @Override
  public boolean isBind(String appId, String varName) {
    AppVariable variable = this.getVariable(appId, varName);
    return variable != null && variable.isBind();
  }

  @Override
  public AppVariable getVariable(String appId, String name) {
    AppVariable variable = variableMapper.getByName(appId, name);
    return variable;
  }

  @Override
  public AppVariable getVariableByAppCode(String appCode, String name) {
    return variableMapper.getByAppCode(appCode, name);
  }


  @Override
  public List<AppVariable> getVariables(long userId, String appid, String keyWord, int offset,
      int count) {
    return variableMapper.getAppVariables(userId, appid, keyWord, offset, count);
  }

  @Override
  @Transactional
  public void applyUpdate(String taskName, String jobName, Map<String, Object> values) {
    List<AppVariable> variables = variableMapper.getAppVariablesByJob(taskName, jobName);
    if (variables == null || variables.size() == 0) {
      return;
    }

    for (AppVariable variable : variables) {
      String param = variable.getBindParam();
      if (!values.containsKey(param)) {
        continue;
      }
      Object value = values.get(param);
      VariableValue currentValue = variable.getCurrentValue();
      if (currentValue == null) {
        variable.setInitTime(new Date());
      }
      if (VariableType.Indicator.equals(variable.getType())) {
        //indicator value process
        IndicatorValue indicatorValue = new IndicatorValue();
        indicatorValue.setParamType(variable.getParamType());
        indicatorValue.setValue(value);
        variable.setPreviousValue(currentValue);
        variable.setCurrentValue(indicatorValue);
        variable.setUpTime(new Date());
        String lockName = this.lockName(variable.getAppId(), variable.getName());
        try {
          this.dbLock.tryLock(lockName);
          variableMapper.update(variable);
        } finally {
          dbLock.releaseLock(lockName);
        }

      } else if (VariableType.Accumulator.equals(variable.getType())) {
        // accumulator value process
        AccumulatorValue accumulatorValue = new AccumulatorValue();
        accumulatorValue.setValue(variable.getCurrentValue().getValue());
        accumulatorValue.setParamType(variable.getParamType());
        accumulatorValue.applyUpdate(value);
        variable.setPreviousValue(currentValue);
        variable.setCurrentValue(accumulatorValue);
        variable.setUpTime(new Date());
        String lockName = this.lockName(variable.getAppId(), variable.getName());
        try {
          dbLock.tryLock(lockName);
          variableMapper.update(variable);
        } finally {
          dbLock.releaseLock(lockName);
        }
      } else if (VariableType.CheckPoint.equals(variable.getType())) {
        if (ParamType.isEmpty(variable.getParamType(), value)) {
          return;
        }
        CheckPointValue checkPointValue = new CheckPointValue();
        checkPointValue.setValue(value);
        checkPointValue.setParamType(variable.getParamType());
        variable.setPreviousValue(currentValue);
        variable.setCurrentValue(checkPointValue);
        variable.setUpTime(new Date());
        String lockName = this.lockName(variable.getAppId(), variable.getName());
        try {
          dbLock.tryLock(lockName);
          variableMapper.update(variable);
        } finally {
          dbLock.releaseLock(lockName);
        }
      } else {
        // queue value process
        //append into queue
      }
    }

  }

  private String lockName(String appId, String varName) {
    return appId + "_" + varName;
  }
}
